﻿
using System;

namespace Family.Core.Model
{
    public abstract class EntityWithTypeId<TId> where TId : struct
    {
        /// <summary>
        /// Gets or sets the id of the entity.
        /// </summary>
        /// <value>The id of the entity.</value>
        public virtual TId Id { get; protected set; }

        /// <summary>
        /// To help ensure hashcode uniqueness, a carefully selected random number multiplier 
        /// is used within the calculation.  Goodrich and Tamassia's Data Structures and
        /// Algorithms in Java asserts that 31, 33, 37, 39 and 41 will produce the fewest number
        /// of collissions.  See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/
        /// for more information.
        /// </summary>
        private const int HASH_MULTIPLIER = 31;

        /// <summary>
        /// cached hash code
        /// </summary>
        private int? cachedHashcode;

        /// <summary>
        /// Determines whether this instance is transient.
        /// </summary>
        /// <returns>
        ///     <c>true</c> if this instance is transient; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool IsTransient()
        {
            Boolean isTransient = this.Id.Equals(default(Int32));

            return isTransient;
        }

        #region Entity comparison support

        /// <summary>
        /// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
        /// </summary>
        /// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
        /// <returns>
        ///     <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
        /// </returns>
        /// <exception cref="T:System.NullReferenceException">
        /// The <paramref name="obj"/> parameter is <see langword="null"/>.
        /// </exception>
        public override bool Equals(Object obj)
        {
            EntityWithTypeId<TId> compareTo = obj as EntityWithTypeId<TId>;

            if (compareTo == null)
            {
                return false;
            }

            if (ReferenceEquals(this, compareTo))
            {
                return true;
            }

            if (this.HasSameNonDefaultIdAs(compareTo))
            {
                return true;
            }

            return this.IsTransient() && compareTo.IsTransient();
        }

        /// <summary>
        /// Returns a hash code for this instance.
        /// </summary>
        /// <returns>
        /// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. 
        /// </returns>
        public override int GetHashCode()
        {
            if (this.cachedHashcode.HasValue)
            {
                return this.cachedHashcode.Value;
            }

            if (this.IsTransient())
            {
                this.cachedHashcode = base.GetHashCode();
            }
            else
            {
                unchecked
                {
                    // It's possible for two objects to return the same hash code based on 
                    // identically valued properties, even if they're of two different types, 
                    // so we include the object's type in the hash calculation
                    int hashCode = GetType().GetHashCode();
                    this.cachedHashcode = (hashCode * HASH_MULTIPLIER) ^ this.Id.GetHashCode();
                }
            }

            return this.cachedHashcode.Value;
        }

        /// <summary>
        /// Returns true if self and the provided entity have the same Id values and the Ids are not of the default Id value
        /// </summary>
        /// <param name="compareTo">The compare to.</param>
        /// <returns>
        ///      <c>true</c> if Ids are the same and not default; otherwise, <c>false</c>.
        /// </returns>
        private bool HasSameNonDefaultIdAs(EntityWithTypeId<TId> compareTo)
        {
            return !this.IsTransient() &&
                   !compareTo.IsTransient() &&
                   this.Id.Equals(compareTo.Id);
        }

        #endregion
    }
}
